package state


import "math"
import "number"
// import "fmt"


type luaTable struct {

	metatable	*luaTable 	// 元表

	arr		[]luaValue		// 存放数组部分  // 声明数组会自动生成空数组, 可以直接进行数组相关的操作

	_map	map[luaValue]luaValue	// 存放哈希表部分  // 声明哈希表, 哈希表默认为空

	// 由于Go语言的map不保证遍历顺序(甚至同一内容的map, 两次遍历的顺序也可能不一样)
	// 所以我们只能在遍历开始之前把所有的键固定下来，保存在keys字段里
	keys	map[luaValue]luaValue
	changed	bool
	lastKey	luaValue
}


// 创建一个空的表
// 该函数接受两个参数，用于预估表的用途和容量
// 如果参数nArr大于0, 说明表可能是当作数组使用的，先创建数组部分
// 如果参数nRec大于0，说明表可能是当作记录使用的，先创建哈希表部分
func newLuaTable(nArr, nRec int) *luaTable {

	t := &luaTable{}

	if nArr > 0 {
		t.arr = make([]luaValue, 0, nArr)
	}

	if nRec > 0 {
		t._map = make(map[luaValue]luaValue, nRec)
	}

	return t
}


// 根据键从表里查找值
// 如果键是整数(或者能够转换为整数的浮点数)，且在数组索引范围之内，直接按索引访问数组部分就可以了
// 否则从哈希表里查找值
func (self *luaTable) get(key luaValue) luaValue {

	key = _floatToInteger(key)

	if idx, ok := key.(int64); ok {

		if idx >= 1 && idx <= int64(len(self.arr)) {

			return self.arr[idx - 1]
		}
	}

	return self._map[key]
}


// 尝试吧浮点数类型的转转成整数
func _floatToInteger(key luaValue) luaValue {

	if f, ok := key.(float64); ok {

		if i, ok := number.FloatToInteger(f); ok {

			return i
		}
	}

	return key
}


// 往表里存入键值对
// 该方法要动态调整数组和哈希表这两个部分
// 测试数据:
// t[nil] = ""
// t[math.NaN] = ""
// t = {1, 2, 3, 4, 5, [7]="7"}
// t[3] = 3.2
// t[6] = 6
// t[6] = nil
// print(#t) 此时官方实现为5, 但这个实现的结果是7 # BUG
func (self *luaTable) put(key, val luaValue) {

	// 判断键是否是nil
	if key == nil {

		panic("table index is nil!")
	}

	// 判断键是否是NaN
	if f, ok := key.(float64); ok && math.IsNaN(f) {

		panic("table index is NaN!")
	}

	self.changed = true

	key = _floatToInteger(key)

	// 如果键是(或者已经被转换为)整数，且在数组索引范围内的话
	// 直接按索引修改数组元素就可以了
	// 向数组里放入nil值会制造洞
	// 如果洞在数组末尾的话，调用_shrinkArray()函数把尾部的洞全部删除
	// 如果键是整数，而且刚刚超出数组索引范围而且值不是nil
	// 就把值追加到数组末尾，然后调用_expandArray()函数动态扩展数组
	if idx, ok := key.(int64); ok && idx >= 1 {

		arrLen := int64(len(self.arr))

		if idx <= arrLen {

			self.arr[idx - 1] = val

			if idx == arrLen && val == nil {

				self._shrinkArray()
			}
			return
		}

		if idx == arrLen + 1 {

			delete(self._map, key)

			if val != nil {		// 插入的值是否为空

				self.arr = append(self.arr, val)

				self._expandArray()
			}

			return
		}
	}

	// 如果值不是nil就把键值写入哈希表
	// 否则把键从哈希表里删除以节约空间
	// 由于在创建表的时候并不一定创建了哈希表部分
	// 所以在第一次写入时，需要创建哈希表
	if val != nil {

		if self._map == nil {

			self._map = make(map[luaValue]luaValue, 8)
		}

		self._map[key] = val
	} else {

		delete(self._map, key)
	}
}


// 收缩数组，删除尾部的nil
func (self *luaTable) _shrinkArray() {

	for i := len(self.arr) - 1; i >= 0; i-- {

		if self.arr[i] == nil {

			self.arr = self.arr[0:i]
		}
	}
}


// 数组部分动态扩展之后，把原本存在哈希表里的某些值也挪到数组里
func (self *luaTable) _expandArray() {

	for idx := int64(len(self.arr)) + 1; true; idx++ {

		if val, found := self._map[idx]; found {

			delete(self._map, idx)

			self.arr = append(self.arr, val)
		} else {
			break
		}
	}
}


// 表长度
func (self *luaTable) len() int {

	return len(self.arr)
}


// 是否有待查询的元方法字段
func (self *luaTable) hasMetafield(fieldName string) bool {

	return self.metatable != nil && self.metatable.get(fieldName) != nil

}


// nextKey()方法根据传入的键返回表的下一个键
// 该方法完全是为API方法Next()设计的
// 如果传入的键是nil，表示遍历开始，需要把所有的键收集到keys里
// keys的键值对记录了表的键和下一个键的关系
// 因此keys字段初始化好之后，直接根据传入参数取值并返回即可
func (self *luaTable) nextKey(key luaValue) luaValue {

	if self.keys == nil || key == nil {

		self.initKeys()
		self.changed = false

	}

	nextKey := self.keys[key]
	if nextKey == nil && key != nil && key != self.lastKey {
		panic("invalid key to 'next'")
	}

	return nextKey

}


// 由于标准库函数next()和API方法Next()考虑的都是整个表(包括数组部分和关联数组部分)
// 因此initKeys方法需要把数组索引和关联数组键都收集起来
func (self *luaTable) initKeys() {

	self.keys = make(map[luaValue]luaValue)  // 当前key-->下一个key, 第一个key为nil

	var key luaValue = nil

	for i, v := range self.arr {

		if v != nil {
			self.keys[key] = int64(i+1)
			key = int64(i+1)
		}
	}


	for k, v := range self._map {

		if v != nil {

			self.keys[key] = k
			key = k
		}
	}

	self.lastKey = key
}